"""
工具类
"""
import datetime as dt
from django.db import IntegrityError
from functools import wraps
import re
from bddjango import get_MySubQuery
from bddjango import show_ls, show_json
from django.db.models import functions as f
from django.db import models as m
from . import serializers
from . import models


def get_user_ip(request):
    meta = request.META
    if 'HTTP_X_FORWARDED_FOR' in meta:
        ip = meta['HTTP_X_FORWARDED_FOR']
    elif 'REMOTE_ADDR' in meta:
        ip = meta['REMOTE_ADDR']
    else:
        raise ValueError('ip地址获取错误!')

    return ip


def days_hours_minutes(td):
    return td.days, td.seconds//3600, (td.seconds//60)%60


# 判断设备信息
def judge_pc_or_mobile(ua):
    """
    判断访问来源是pc端还是手机端
    :param ua: 访问来源头信息中的User-Agent字段内容      request.META.get("HTTP_USER_AGENT")
    :return:
    """

    factor = ua
    is_mobile = False

    _long_matches = r'googlebot-mobile|android|avantgo|blackberry|blazer|elaine|hiptop|ip(hone|od)|kindle|midp|mmp' \
                    r'|mobile|o2|opera mini|palm( os)?|pda|plucker|pocket|psp|smartphone|symbian|treo|up\.(browser|link)' \
                    r'|vodafone|wap|windows ce; (iemobile|ppc)|xiino|maemo|fennec'
    _long_matches = re.compile(_long_matches, re.IGNORECASE)
    _short_matches = r'1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)' \
                     r'|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)' \
                     r'|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw' \
                     r'|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8' \
                     r'|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit' \
                     r'|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)' \
                     r'|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji' \
                     r'|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx' \
                     r'|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi' \
                     r'|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)' \
                     r'|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg' \
                     r'|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21' \
                     r'|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-' \
                     r'|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it' \
                     r'|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)' \
                     r'|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)' \
                     r'|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit' \
                     r'|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-'

    _short_matches = re.compile(_short_matches, re.IGNORECASE)

    if _long_matches.search(factor) != None:
        is_mobile = True
    user_agent = factor[0:4]
    if _short_matches.search(user_agent) != None:
        is_mobile = True

    return is_mobile


def statistic_view_count(request, hour_span=1):
    """
    统计浏览次数, 同一个ip间隔HOUR_SPAN后算一次不同访问

    :param request: http请求
    :param hour_span: 时间间隔
    :return:
    """
    ip = get_user_ip(request)
    mobile = judge_pc_or_mobile(request.META.get("HTTP_USER_AGENT"))

    qs_ls: m.QuerySet = models.Statistics.objects.filter(ip=ip, mobile=mobile)
    if qs_ls.count():
        # ip在同一个小时内多次访问, 只算访问一次
        qs_i: models.Statistics = qs_ls.order_by('-id')[0]     # 获取最近一次访问数据
        recent = qs_i.access_time or dt.datetime(year=1900, month=1, day=1)
        now = dt.datetime.now()
        try:
            delta = now - recent
        except Exception as e:
            raise TypeError(f'请检测settings的USE_TZ是否设置为False! --- {str(e)}')

        # td = dt.timedelta(days=delta.days, seconds=delta.seconds)     # 这里忽略天数days
        days, hours, minutes = days_hours_minutes(delta)
        total_hour = days * 24 + hours

        if total_hour < hour_span:
            return False
    print('--- new visitor!')

    try:
        qs_ls.create(ip=ip, mobile=mobile)
    except IntegrityError as e:     # 防止并发时重复保存
        print('--- statistics重复:', e)
        pass
    return True


def statistic_decorator(func):
    """
    * 流量统计装饰器
    """

    @wraps(func)
    def wrapped_function(obj_view, request, *args, **kwargs):
        statistic_view_count(request)
        return func(obj_view, request, *args, **kwargs)
    return wrapped_function


def get_statistics_result(qs, field_name='access_date', order=True, distinct=True, get_serializer_data=True):
    SQCount = get_MySubQuery(my_model=qs, field_name='ip', function_name='Count', output_field=m.IntegerField)

    # .values('id', field_name, 'ip', 'mobile')
    # qs = .values('id', field_name, 'ip', 'mobile')

    from django.conf import settings
    def qs_ls_distinct_by_field_ls(qs_ls, field_ls):
        # 这里得判断是不是postgresql, 不然会报错!
        engine = settings.DATABASES.get('default').get('ENGINE')
        if 'postgresql' in engine:
            qs_ls = qs_ls.distinct(field_ls).order_by(field_ls)
        else:
            qs_ls = qs_ls.distinct().order_by(field_ls)
        return qs_ls

    qs = qs.values()
    qs1 = qs.annotate(
        # 该日访问量
        count_date=m.Subquery(
            qs.filter(access_date=m.OuterRef(field_name)).values(field_name).annotate(count=m.Count(field_name)).values(
                'count')
        ),
        # 该日访问量中的mobile数
        _count_date_mobile=m.Subquery(
            qs.filter(access_date=m.OuterRef(field_name), mobile=True).values(field_name).annotate(
                count=m.Count(field_name)).values('count')
        ),
        # 将None转换为0
        count_date_mobile=m.Case(
            m.When(_count_date_mobile__isnull=True, then=m.Value(0)),
            default=m.F('_count_date_mobile'),
            output_field=m.IntegerField()
        ),

        # 该日访问量中的pc数
        _count_date_pc=m.Subquery(
                qs
                    .filter(access_date=m.OuterRef(field_name), mobile=False)
                    .values(field_name)
                    .annotate(count=m.Count(field_name)).values('count')
        ),
        count_date_pc=m.Case(
            m.When(_count_date_pc__isnull=True, then=m.Value(0)),
            default=m.F('_count_date_pc'),
            output_field=m.IntegerField()
        ),

        # 该日访问的不同ip数量
        count_date_ip=SQCount(
            qs_ls_distinct_by_field_ls(qs.filter(access_date=m.OuterRef(field_name)).values('ip'), 'ip')
            # qs.filter(access_date=m.OuterRef(field_name)).values('ip').order_by('ip').distinct()
        ),

        # 日期, 仅适用于postgresql
        # day=f.Cast(f.TruncDate(field_name, m.DateTimeField()), m.CharField()),
    )
    # xx = qs.annotate(day=f.Cast(f.TruncDate(field_name, m.DateTimeField()), m.CharField()), )

    # 通用的获取日期函数
    qs1 = qs1.annotate(
        _month=f.Cast(f.ExtractMonth(field_name, m.DateTimeField()), m.CharField()),
        _day=f.Cast(f.ExtractDay(field_name, m.DateTimeField()), m.CharField()),
        _year=f.Cast(f.ExtractYear(field_name, m.DateTimeField()), m.CharField()),
        day=f.Concat(
            '_year', m.Value('-'), '_month', m.Value('-'), '_day',
            output_field=m.CharField()
        )
    )
    # print(qs1.values('day'))
    # show_ls(qs1.values('_count_date_mobile', 'count_date_mobile'))
    # show_ls(qs1.values('_count_date_pc', 'count_date_pc'))

    ret = qs1
    if order:
        if distinct:
            ret = qs_ls_distinct_by_field_ls(ret, field_name)
        else:
            ret = ret.order_by(field_name)

    if get_serializer_data:
        ret = serializers.ListStatistics(ret, many=True).data

    return ret


